This ASM-Crackme has an unusual protection: Out of 15 buttons we have to press the right ones to enter a 10-digit keycode. It took me a while to fully understand what's going on here and how to solve it. The readme-file is a little misleading, Detten says: "Don't try to bruteforce all combinations, too much work! Instead try to figure out the 'holes' in the code". But we do have to bruteforce to get the valid combination.
Let┤s have a look at the listing first, especially the part where the buttons are being processed with the WM_COMMAND message. Due to the number of buttons, the listing is quite big:
004013ED A3 38 30 40 00 mov dword_403038, eax ; save a
004013F2 89 1D 3C 30 40 00 mov dword_40303C, ebx ; save b
004013F8 89 0D 40 30 40 00 mov dword_403040, ecx ; save c
For every button pressed there are some calculations with the three variables a, b and c. They are saved and a counter is incremented. Back to the main program:
This section is pretty much self explanatory: when we press the About-button, the program shows the About box, and when we press the Clear-button, the three variables a, b, c and the loopcounter are reset. We're getting closer to the heart of the protection now:
If the loopcounter is three, the program shows a nice message: 'Trying to bruteforce?' - I love Detten's humor :-)) We see that the length of the serial is 10, otherwise we jump over call 2 and 3. Call 2 is interesting:
Here we have some SMC - that's the whole trick. The three variables a, b and c are xored with the dwords at address 401407, 40143B and 40143F respectively. These addresses are in call 3. The first byte at address 401407 must be 52h. This is a security measure to check that everything has been well decrypted. Otherwise there might have been produced some garbage instructions that terminate the crackme with an error. Let's have a look at call 3:
00401403 55 push ebp
00401404 8B EC mov ebp, esp
00401406 50 push eax
00401407 EB 3F jmp short locret_401448 ; first dword to be decrypted --> (d)
00401443 E8 78 00 00 00 call j_SetDlgItemTextA ; seems to be the success message
00401448 C9 leave
00401449 C2 08 00 retn 8
Well, this seems to be the success routine - the api function "SetDlgItemTextA" is very suspicious. We've learned in the call before that the three dwords at adress 401407, 40143B and 40143F are xored with variables, whose values depends on which keys have been pressed. We have to "guess" what instruction are missing in order to show a success message.
First of all: Where is the success message? We find two arguments being pushed on the stack before the call:
Hmm, the string 'An error occured' - my Zen-instinct tells me that this is a fishy thing! We singlestep the routine, jump to address 40140B and change register edx to 403000, the offset of the string mentioned. And I was right, the decrypted string is "ACCESS GRANTED !". Well, that's a beginning. We now know, what should be displayed instead of "NO ACCESS".
Fortunately, there's another "SetDlgItemTextA" in the main program, the one that shows the bruteforce message:
00401228 68 11 30 40 00 push offset aTryingToBrutef ; Text = 'Trying to bruteforce?'
All we have to do is change the offset, all the other arguments are the same. The first and last one needed for the API function are being pushed in the main code (see above), we have to adjust call 3 accordingly. We know that byte 401407 has to be 52h, which is a "push edx" instruction. Then there are three bytes left, before the decryption of the string begins. This will only be done properly when register edx is the offset of the string, i.e. 403000. We have to load this into edx, but an ordinary mov edx, 403000 won't fit in three bytes.
That's where the pushed arguments are needed. It's a ebp-based subroutine, and therefore we can find the pushed arguments at [ebp+x]. (If you don't know what I'm talking about, read some asm-related tuts. There once was a very good thread covering this topic on woodmanns/fravias board. This tut is already getting too long anyway...). We find the correct argument at [ebp+0C]. Therefore the right instruction is:
00401407 52 push edx
00401408 8B 55 0C mov edx, [ebp+C]
OK, the first dword is solved. After having decrypted the string, the program needs to pass the three needed arguments on the stack as shown in the example with the "bruteforce" string. We know the offset of the success string is [ebp+C], and pushing this is a three-byte instruction. Then we need to push the controll id, which is a two byte-instruction. Finally we need to push the handle, which has been passed on the stack together with the encrypted string. We find it at [ebp+8], and it's a three-byte instruction. This is 3 + 2 + 3 = 8 bytes = 2 dwords - BINGO!
0040143B FF 75 0C push dword ptr [ebp+c]
0040143E 6A 03 push 3
00401440 FF 75 08 push dword ptr [ebp+8]
We trace this in Softice and adjust the memory accordingly - and it works! Now the main work has been done, we "only" need to bruteforce the right combination. We have:
These are the values of our three variables a, b and c. After xoring them with the corresponding addresses 401407, 40143B and 40143F we get the right opcodes to have the success message displayed. In the messageboard of Dettens page he gives us the first two keys: 79. Otherwise the bruteforcing might take too long. One thing to remember: All calculations take place in registers, thats why all the dwords from memory must be in reverse order (little endian).